/*
 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.provider.certpath;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.*;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.interfaces.DSAPublicKey;
import javax.security.auth.x500.X500Principal;
import sun.security.util.Debug;
import sun.security.x509.AccessDescription;
import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.CRLDistributionPointsExtension;
import sun.security.x509.DistributionPoint;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.PKIXExtensions;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CRLEntryImpl;

/**
 * CrlRevocationChecker is a <code>PKIXCertPathChecker</code> that checks
 * revocation status information on a PKIX certificate using CRLs obtained
 * from one or more <code>CertStores</code>. This is based on section 6.3
 * of RFC 3280 (http://www.ietf.org/rfc/rfc3280.txt).
 *
 * @since       1.4
 * @author      Seth Proctor
 * @author      Steve Hanna
 */
class CrlRevocationChecker extends PKIXCertPathChecker {

    private static final Debug debug = Debug.getInstance("certpath");
    private final TrustAnchor mAnchor;
    private final List<CertStore> mStores;
    private final String mSigProvider;
    private final Date mCurrentTime;
    private PublicKey mPrevPubKey;
    private boolean mCRLSignFlag;
    private HashSet<X509CRL> mPossibleCRLs;
    private HashSet<X509CRL> mApprovedCRLs;
    private final PKIXParameters mParams;
    private static final boolean [] mCrlSignUsage =
        { false, false, false, false, false, false, true };
    private static final boolean[] ALL_REASONS =
        {true, true, true, true, true, true, true, true, true};
    private boolean mOnlyEECert = false;

    // Maximum clock skew in milliseconds (15 minutes) allowed when checking
    // validity of CRLs
    private static final long MAX_CLOCK_SKEW = 900000;

    /**
     * Creates a <code>CrlRevocationChecker</code>.
     *
     * @param anchor anchor selected to validate the target certificate
     * @param params <code>PKIXParameters</code> to be used for
     *               finding certificates and CRLs, etc.
     */
    CrlRevocationChecker(TrustAnchor anchor, PKIXParameters params)
        throws CertPathValidatorException
    {
        this(anchor, params, null);
    }

    /**
     * Creates a <code>CrlRevocationChecker</code>, allowing
     * extra certificates to be supplied beyond those contained
     * in the <code>PKIXParameters</code>.
     *
     * @param anchor anchor selected to validate the target certificate
     * @param params <code>PKIXParameters</code> to be used for
     *               finding certificates and CRLs, etc.
     * @param certs a <code>Collection</code> of certificates
     *              that may be useful, beyond those available
     *              through <code>params</code> (<code>null</code>
     *              if none)
     */
    CrlRevocationChecker(TrustAnchor anchor, PKIXParameters params,
        Collection<X509Certificate> certs) throws CertPathValidatorException
    {
        this(anchor, params, certs, false);
    }

    CrlRevocationChecker(TrustAnchor anchor, PKIXParameters params,
        Collection<X509Certificate> certs, boolean onlyEECert)
        throws CertPathValidatorException {
        mAnchor = anchor;
        mParams = params;
        mStores = new ArrayList<CertStore>(params.getCertStores());
        mSigProvider = params.getSigProvider();
        if (certs != null) {
            try {
                mStores.add(CertStore.getInstance("Collection",
                    new CollectionCertStoreParameters(certs)));
            } catch (Exception e) {
                // should never occur but not necessarily fatal, so log it,
                // ignore and continue
                if (debug != null) {
                    debug.println("CrlRevocationChecker: " +
                        "error creating Collection CertStore: " + e);
                }
            }
        }
        Date testDate = params.getDate();
        mCurrentTime = (testDate != null ? testDate : new Date());
        mOnlyEECert = onlyEECert;
        init(false);
    }

    /**
     * Initializes the internal state of the checker from parameters
     * specified in the constructor
     */
    public void init(boolean forward) throws CertPathValidatorException
    {
        if (!forward) {
            if (mAnchor != null) {
                if (mAnchor.getCAPublicKey() != null) {
                    mPrevPubKey = mAnchor.getCAPublicKey();
                } else {
                    mPrevPubKey = mAnchor.getTrustedCert().getPublicKey();
                }
            } else {
                mPrevPubKey = null;
            }
            mCRLSignFlag = true;
        } else {
            throw new CertPathValidatorException("forward checking "
                                + "not supported");
        }
    }

    public boolean isForwardCheckingSupported() {
        return false;
    }

    public Set<String> getSupportedExtensions() {
        return null;
    }

    /**
     * Performs the revocation status check on the certificate using
     * its internal state.
     *
     * @param cert the Certificate
     * @param unresolvedCritExts a Collection of the unresolved critical
     * extensions
     * @exception CertPathValidatorException Exception thrown if
     * certificate does not verify
     */
    public void check(Certificate cert, Collection<String> unresolvedCritExts)
        throws CertPathValidatorException
    {
        X509Certificate currCert = (X509Certificate) cert;
        verifyRevocationStatus(currCert, mPrevPubKey, mCRLSignFlag, true);

        // Make new public key if parameters are missing
        PublicKey cKey = currCert.getPublicKey();
        if (cKey instanceof DSAPublicKey &&
            ((DSAPublicKey)cKey).getParams() == null) {
            // cKey needs to inherit DSA parameters from prev key
            cKey = BasicChecker.makeInheritedParamsKey(cKey, mPrevPubKey);
        }
        mPrevPubKey = cKey;
        mCRLSignFlag = certCanSignCrl(currCert);
    }

    /**
     * Performs the revocation status check on the certificate using
     * the provided state variables, as well as the constant internal
     * data.
     *
     * @param currCert the Certificate
     * @param prevKey the previous PublicKey in the chain
     * @param signFlag a boolean as returned from the last call, or true
     * if this is the first cert in the chain
     * @return a boolean specifying if the cert is allowed to vouch for the
     * validity of a CRL for the next iteration
     * @exception CertPathValidatorException Exception thrown if
     *            certificate does not verify.
     */
    public boolean check(X509Certificate currCert, PublicKey prevKey,
        boolean signFlag) throws CertPathValidatorException
    {
        verifyRevocationStatus(currCert, prevKey, signFlag, true);
        return certCanSignCrl(currCert);
    }

    /**
     * Checks that a cert can be used to verify a CRL.
     *
     * @param currCert an X509Certificate to check
     * @return a boolean specifying if the cert is allowed to vouch for the
     * validity of a CRL
     */
    static boolean certCanSignCrl(X509Certificate currCert) {
        // if the cert doesn't include the key usage ext, or
        // the key usage ext asserts cRLSigning, return true,
        // otherwise return false.
        boolean[] kbools = currCert.getKeyUsage();
        if (kbools != null) {
            return kbools[6];
        }
        return false;
    }

    /**
     * Internal method to start the verification of a cert
     */
    private void verifyRevocationStatus(X509Certificate currCert,
        PublicKey prevKey, boolean signFlag, boolean allowSeparateKey)
        throws CertPathValidatorException
    {
        verifyRevocationStatus(currCert, prevKey, signFlag,
                   allowSeparateKey, null, mParams.getTrustAnchors());
    }

    /**
     * Internal method to start the verification of a cert
     * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s>
     *                     whose revocation status depends on the
     *                     non-revoked status of this cert. To avoid
     *                     circular dependencies, we assume they're
     *                     revoked while checking the revocation
     *                     status of this cert.
     * @param trustAnchors a <code>Set</code> of <code>TrustAnchor</code>s
     */
    private void verifyRevocationStatus(X509Certificate currCert,
        PublicKey prevKey, boolean signFlag, boolean allowSeparateKey,
        Set<X509Certificate> stackedCerts,
        Set<TrustAnchor> trustAnchors) throws CertPathValidatorException {

        String msg = "revocation status";
        if (debug != null) {
            debug.println("CrlRevocationChecker.verifyRevocationStatus()" +
                " ---checking " + msg + "...");
        }

        if (mOnlyEECert && currCert.getBasicConstraints() != -1) {
            if (debug != null) {
                debug.println("Skipping revocation check, not end entity cert");
            }
            return;
        }

        // reject circular dependencies - RFC 3280 is not explicit on how
        // to handle this, so we feel it is safest to reject them until
        // the issue is resolved in the PKIX WG.
        if ((stackedCerts != null) && stackedCerts.contains(currCert)) {
            if (debug != null) {
                debug.println("CrlRevocationChecker.verifyRevocationStatus()" +
                    " circular dependency");
            }
            throw new CertPathValidatorException
                ("Could not determine revocation status", null, null, -1,
                 BasicReason.UNDETERMINED_REVOCATION_STATUS);
        }

        // init the state for this run
        mPossibleCRLs = new HashSet<X509CRL>();
        mApprovedCRLs = new HashSet<X509CRL>();
        boolean[] reasonsMask = new boolean[9];

        try {
            X509CRLSelector sel = new X509CRLSelector();
            sel.setCertificateChecking(currCert);
            CertPathHelper.setDateAndTime(sel, mCurrentTime, MAX_CLOCK_SKEW);

            for (CertStore mStore : mStores) {
                for (java.security.cert.CRL crl : mStore.getCRLs(sel)) {
                    mPossibleCRLs.add((X509CRL)crl);
                }
            }
            DistributionPointFetcher store =
                DistributionPointFetcher.getInstance();
            // all CRLs returned by the DP Fetcher have also been verified
            mApprovedCRLs.addAll(store.getCRLs(sel, signFlag, prevKey,
                mSigProvider, mStores, reasonsMask, trustAnchors,
                mParams.getDate()));
        } catch (Exception e) {
            if (debug != null) {
                debug.println("CrlRevocationChecker.verifyRevocationStatus() "
                    + "unexpected exception: " + e.getMessage());
            }
            throw new CertPathValidatorException(e);
        }

        if (debug != null) {
            debug.println("CrlRevocationChecker.verifyRevocationStatus() " +
                "crls.size() = " + mPossibleCRLs.size());
        }
        if (!mPossibleCRLs.isEmpty()) {
            // Now that we have a list of possible CRLs, see which ones can
            // be approved
            mApprovedCRLs.addAll(verifyPossibleCRLs(mPossibleCRLs, currCert,
                signFlag, prevKey, reasonsMask, trustAnchors));
        }
        if (debug != null) {
            debug.println("CrlRevocationChecker.verifyRevocationStatus() " +
                "approved crls.size() = " + mApprovedCRLs.size());
        }

        // make sure that we have at least one CRL that _could_ cover
        // the certificate in question and all reasons are covered
        if (mApprovedCRLs.isEmpty() ||
            !Arrays.equals(reasonsMask, ALL_REASONS)) {
            if (allowSeparateKey) {
                verifyWithSeparateSigningKey(currCert, prevKey, signFlag,
                                             stackedCerts);
                return;
            } else {
                throw new CertPathValidatorException
                ("Could not determine revocation status", null, null, -1,
                 BasicReason.UNDETERMINED_REVOCATION_STATUS);
            }
        }

        // See if the cert is in the set of approved crls.
        if (debug != null) {
            BigInteger sn = currCert.getSerialNumber();
            debug.println("CrlRevocationChecker.verifyRevocationStatus() " +
                            "starting the final sweep...");
            debug.println("CrlRevocationChecker.verifyRevocationStatus" +
                            " cert SN: " + sn.toString());
        }

        CRLReason reasonCode = CRLReason.UNSPECIFIED;
        X509CRLEntryImpl entry = null;
        for (X509CRL crl : mApprovedCRLs) {
            X509CRLEntry e = crl.getRevokedCertificate(currCert);
            if (e != null) {
                try {
                    entry = X509CRLEntryImpl.toImpl(e);
                } catch (CRLException ce) {
                    throw new CertPathValidatorException(ce);
                }
                if (debug != null) {
                    debug.println("CrlRevocationChecker.verifyRevocationStatus"
                        + " CRL entry: " + entry.toString());
                }

                /*
                 * Abort CRL validation and throw exception if there are any
                 * unrecognized critical CRL entry extensions (see section
                 * 5.3 of RFC 3280).
                 */
                Set<String> unresCritExts = entry.getCriticalExtensionOIDs();
                if (unresCritExts != null && !unresCritExts.isEmpty()) {
                    /* remove any that we will process */
                    unresCritExts.remove
                        (PKIXExtensions.ReasonCode_Id.toString());
                    unresCritExts.remove
                        (PKIXExtensions.CertificateIssuer_Id.toString());
                    if (!unresCritExts.isEmpty()) {
                        if (debug != null) {
                            debug.println("Unrecognized "
                            + "critical extension(s) in revoked CRL entry: "
                            + unresCritExts);
                        }
                        throw new CertPathValidatorException
                        ("Could not determine revocation status", null, null,
                         -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
                    }
                }

                reasonCode = entry.getRevocationReason();
                if (reasonCode == null) {
                    reasonCode = CRLReason.UNSPECIFIED;
                }
                Throwable t = new CertificateRevokedException
                    (entry.getRevocationDate(), reasonCode,
                     crl.getIssuerX500Principal(), entry.getExtensions());
                throw new CertPathValidatorException(t.getMessage(), t,
                    null, -1, BasicReason.REVOKED);
            }
        }
    }

    /**
     * We have a cert whose revocation status couldn't be verified by
     * a CRL issued by the cert that issued the CRL. See if we can
     * find a valid CRL issued by a separate key that can verify the
     * revocation status of this certificate.
     * <p>
     * Note that this does not provide support for indirect CRLs,
     * only CRLs signed with a different key (but the same issuer
     * name) as the certificate being checked.
     *
     * @param currCert the <code>X509Certificate</code> to be checked
     * @param prevKey the <code>PublicKey</code> that failed
     * @param signFlag <code>true</code> if that key was trusted to sign CRLs
     * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s>
     *                     whose revocation status depends on the
     *                     non-revoked status of this cert. To avoid
     *                     circular dependencies, we assume they're
     *                     revoked while checking the revocation
     *                     status of this cert.
     * @throws CertPathValidatorException if the cert's revocation status
     *         cannot be verified successfully with another key
     */
    private void verifyWithSeparateSigningKey(X509Certificate currCert,
        PublicKey prevKey, boolean signFlag, Set<X509Certificate> stackedCerts)
        throws CertPathValidatorException {
        String msg = "revocation status";
        if (debug != null) {
            debug.println(
                "CrlRevocationChecker.verifyWithSeparateSigningKey()" +
                " ---checking " + msg + "...");
        }

        // reject circular dependencies - RFC 3280 is not explicit on how
        // to handle this, so we feel it is safest to reject them until
        // the issue is resolved in the PKIX WG.
        if ((stackedCerts != null) && stackedCerts.contains(currCert)) {
            if (debug != null) {
                debug.println(
                    "CrlRevocationChecker.verifyWithSeparateSigningKey()" +
                    " circular dependency");
            }
            throw new CertPathValidatorException
                ("Could not determine revocation status", null, null,
                 -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
        }

        // If prevKey wasn't trusted, maybe we just didn't have the right
        // path to it. Don't rule that key out.
        if (!signFlag) {
            prevKey = null;
        }

        // Try to find another key that might be able to sign
        // CRLs vouching for this cert.
        buildToNewKey(currCert, prevKey, stackedCerts);
    }

    /**
     * Tries to find a CertPath that establishes a key that can be
     * used to verify the revocation status of a given certificate.
     * Ignores keys that have previously been tried. Throws a
     * CertPathValidatorException if no such key could be found.
     *
     * @param currCert the <code>X509Certificate</code> to be checked
     * @param prevKey the <code>PublicKey</code> of the certificate whose key
     *    cannot be used to vouch for the CRL and should be ignored
     * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s>
     *                     whose revocation status depends on the
     *                     establishment of this path.
     * @throws CertPathValidatorException on failure
     */
    private void buildToNewKey(X509Certificate currCert,
        PublicKey prevKey, Set<X509Certificate> stackedCerts)
        throws CertPathValidatorException {

        if (debug != null) {
            debug.println("CrlRevocationChecker.buildToNewKey()" +
                          " starting work");
        }
        Set<PublicKey> badKeys = new HashSet<PublicKey>();
        if (prevKey != null) {
            badKeys.add(prevKey);
        }
        X509CertSelector certSel = new RejectKeySelector(badKeys);
        certSel.setSubject(currCert.getIssuerX500Principal());
        certSel.setKeyUsage(mCrlSignUsage);

        Set<TrustAnchor> newAnchors =
            (mAnchor == null ? mParams.getTrustAnchors() :
                                Collections.singleton(mAnchor));

        PKIXBuilderParameters builderParams;
        if (mParams instanceof PKIXBuilderParameters) {
            builderParams = (PKIXBuilderParameters) mParams.clone();
            builderParams.setTargetCertConstraints(certSel);
            // Policy qualifiers must be rejected, since we don't have
            // any way to convey them back to the application.
            builderParams.setPolicyQualifiersRejected(true);
            try {
                builderParams.setTrustAnchors(newAnchors);
            } catch (InvalidAlgorithmParameterException iape) {
                throw new RuntimeException(iape); // should never occur
            }
        } else {
            // It's unfortunate that there's no easy way to make a
            // PKIXBuilderParameters object from a PKIXParameters
            // object. This might miss some things if parameters
            // are added in the future or the validatorParams object
            // is a custom class derived from PKIXValidatorParameters.
            try {
                builderParams = new PKIXBuilderParameters(newAnchors, certSel);
            } catch (InvalidAlgorithmParameterException iape) {
                throw new RuntimeException(iape); // should never occur
            }
            builderParams.setInitialPolicies(mParams.getInitialPolicies());
            builderParams.setCertStores(mStores);
            builderParams.setExplicitPolicyRequired
                (mParams.isExplicitPolicyRequired());
            builderParams.setPolicyMappingInhibited
                (mParams.isPolicyMappingInhibited());
            builderParams.setAnyPolicyInhibited(mParams.isAnyPolicyInhibited());
            // Policy qualifiers must be rejected, since we don't have
            // any way to convey them back to the application.
            // That's the default, so no need to write code.
            builderParams.setDate(mParams.getDate());
            builderParams.setCertPathCheckers(mParams.getCertPathCheckers());
            builderParams.setSigProvider(mParams.getSigProvider());
        }

        // Skip revocation during this build to detect circular
        // references. But check revocation afterwards, using the
        // key (or any other that works).
        builderParams.setRevocationEnabled(false);

        // check for AuthorityInformationAccess extension
        if (Builder.USE_AIA == true) {
            X509CertImpl currCertImpl = null;
            try {
                currCertImpl = X509CertImpl.toImpl(currCert);
            } catch (CertificateException ce) {
                // ignore but log it
                if (debug != null) {
                    debug.println("CrlRevocationChecker.buildToNewKey: " +
                        "error decoding cert: " + ce);
                }
            }
            AuthorityInfoAccessExtension aiaExt = null;
            if (currCertImpl != null) {
                aiaExt = currCertImpl.getAuthorityInfoAccessExtension();
            }
            if (aiaExt != null) {
                List<AccessDescription> adList = aiaExt.getAccessDescriptions();
                if (adList != null) {
                    for (AccessDescription ad : adList) {
                        CertStore cs = URICertStore.getInstance(ad);
                        if (cs != null) {
                            if (debug != null) {
                                debug.println("adding AIAext CertStore");
                            }
                            builderParams.addCertStore(cs);
                        }
                    }
                }
            }
        }

        CertPathBuilder builder = null;
        try {
            builder = CertPathBuilder.getInstance("PKIX");
        } catch (NoSuchAlgorithmException nsae) {
            throw new CertPathValidatorException(nsae);
        }
        while (true) {
            try {
                if (debug != null) {
                    debug.println("CrlRevocationChecker.buildToNewKey()" +
                                  " about to try build ...");
                }
                PKIXCertPathBuilderResult cpbr =
                    (PKIXCertPathBuilderResult) builder.build(builderParams);

                if (debug != null) {
                    debug.println("CrlRevocationChecker.buildToNewKey()" +
                                  " about to check revocation ...");
                }
                // Now check revocation of all certs in path, assuming that
                // the stackedCerts are revoked.
                if (stackedCerts == null) {
                    stackedCerts = new HashSet<X509Certificate>();
                }
                stackedCerts.add(currCert);
                TrustAnchor ta = cpbr.getTrustAnchor();
                PublicKey prevKey2 = ta.getCAPublicKey();
                if (prevKey2 == null) {
                    prevKey2 = ta.getTrustedCert().getPublicKey();
                }
                boolean signFlag = true;
                List<? extends Certificate> cpList =
                    cpbr.getCertPath().getCertificates();
                try {
                    for (int i = cpList.size()-1; i >= 0; i-- ) {
                        X509Certificate cert = (X509Certificate) cpList.get(i);

                        if (debug != null) {
                            debug.println("CrlRevocationChecker.buildToNewKey()"
                                + " index " + i + " checking " + cert);
                        }
                        verifyRevocationStatus(cert, prevKey2, signFlag, true,
                                stackedCerts, newAnchors);
                        signFlag = certCanSignCrl(cert);
                        prevKey2 = cert.getPublicKey();
                    }
                } catch (CertPathValidatorException cpve) {
                    // ignore it and try to get another key
                    badKeys.add(cpbr.getPublicKey());
                    continue;
                }

                if (debug != null) {
                    debug.println("CrlRevocationChecker.buildToNewKey()" +
                                  " got key " + cpbr.getPublicKey());
                }
                // Now check revocation on the current cert using that key.
                // If it doesn't check out, try to find a different key.
                // And if we can't find a key, then return false.
                PublicKey newKey = cpbr.getPublicKey();
                try {
                    verifyRevocationStatus(currCert, newKey, true, false);
                    // If that passed, the cert is OK!
                    return;
                } catch (CertPathValidatorException cpve) {
                    // If it is revoked, rethrow exception
                    if (cpve.getReason() == BasicReason.REVOKED) {
                        throw cpve;
                    }
                    // Otherwise, ignore the exception and
                    // try to get another key.
                }
                badKeys.add(newKey);
            } catch (InvalidAlgorithmParameterException iape) {
                throw new CertPathValidatorException(iape);
            } catch (CertPathBuilderException cpbe) {
                throw new CertPathValidatorException
                    ("Could not determine revocation status", null, null,
                     -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
            }
        }
    }

    /*
     * This inner class extends the X509CertSelector to add an additional
     * check to make sure the subject public key isn't on a particular list.
     * This class is used by buildToNewKey() to make sure the builder doesn't
     * end up with a CertPath to a public key that has already been rejected.
     */
    private static class RejectKeySelector extends X509CertSelector {
        private final Set<PublicKey> badKeySet;

        /**
         * Creates a new <code>RejectKeySelector</code>.
         *
         * @param badPublicKeys a <code>Set</code> of
         *                      <code>PublicKey</code>s that
         *                      should be rejected (or <code>null</code>
         *                      if no such check should be done)
         */
        RejectKeySelector(Set<PublicKey> badPublicKeys) {
            this.badKeySet = badPublicKeys;
        }

        /**
         * Decides whether a <code>Certificate</code> should be selected.
         *
         * @param cert the <code>Certificate</code> to be checked
         * @return <code>true</code> if the <code>Certificate</code> should be
         *         selected, <code>false</code> otherwise
         */
        public boolean match(Certificate cert) {
            if (!super.match(cert))
                return(false);

            if (badKeySet.contains(cert.getPublicKey())) {
                if (debug != null)
                    debug.println("RejectCertSelector.match: bad key");
                return false;
            }

            if (debug != null)
                debug.println("RejectCertSelector.match: returning true");
            return true;
        }

        /**
         * Return a printable representation of the <code>CertSelector</code>.
         *
         * @return a <code>String</code> describing the contents of the
         *         <code>CertSelector</code>
         */
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("RejectCertSelector: [\n");
            sb.append(super.toString());
            sb.append(badKeySet);
            sb.append("]");
            return sb.toString();
        }
    }

    /**
     * Internal method that verifies a set of possible_crls,
     * and sees if each is approved, based on the cert.
     *
     * @param crls a set of possible CRLs to test for acceptability
     * @param cert the certificate whose revocation status is being checked
     * @param signFlag <code>true</code> if prevKey was trusted to sign CRLs
     * @param prevKey the public key of the issuer of cert
     * @param reasonsMask the reason code mask
     * @param trustAnchors a <code>Set</code> of <code>TrustAnchor</code>s>
     * @return a collection of approved crls (or an empty collection)
     */
    private Collection<X509CRL> verifyPossibleCRLs(Set<X509CRL> crls,
        X509Certificate cert, boolean signFlag, PublicKey prevKey,
        boolean[] reasonsMask,
        Set<TrustAnchor> trustAnchors) throws CertPathValidatorException {

        try {
            X509CertImpl certImpl = X509CertImpl.toImpl(cert);
            if (debug != null) {
                debug.println("CRLRevocationChecker.verifyPossibleCRLs: " +
                        "Checking CRLDPs for "
                        + certImpl.getSubjectX500Principal());
            }
            CRLDistributionPointsExtension ext =
                certImpl.getCRLDistributionPointsExtension();
            List<DistributionPoint> points = null;
            if (ext == null) {
                // assume a DP with reasons and CRLIssuer fields omitted
                // and a DP name of the cert issuer.
                // TODO add issuerAltName too
                X500Name certIssuer = (X500Name)certImpl.getIssuerDN();
                DistributionPoint point = new DistributionPoint
                    (new GeneralNames().add(new GeneralName(certIssuer)),
                     null, null);
                points = Collections.singletonList(point);
            } else {
                points = (List<DistributionPoint>)ext.get(
                                        CRLDistributionPointsExtension.POINTS);
            }
            Set<X509CRL> results = new HashSet<X509CRL>();
            DistributionPointFetcher dpf =
                DistributionPointFetcher.getInstance();
            for (Iterator<DistributionPoint> t = points.iterator();
                 t.hasNext() && !Arrays.equals(reasonsMask, ALL_REASONS); ) {
                DistributionPoint point = t.next();
                for (X509CRL crl : crls) {
                    if (dpf.verifyCRL(certImpl, point, crl, reasonsMask,
                            signFlag, prevKey, mSigProvider,
                            trustAnchors, mStores, mParams.getDate())) {
                        results.add(crl);
                    }
                }
            }
            return results;
        } catch (Exception e) {
            if (debug != null) {
                debug.println("Exception while verifying CRL: "+e.getMessage());
                e.printStackTrace();
            }
            return Collections.emptySet();
        }
    }
}
